package domains;

import java.math.BigInteger;

public class IntervalBound {
	
	private boolean isMinusInfinity;
	
	private boolean isPlusInfinity;
	
	private BigInteger bound;
	
	public IntervalBound(BigInteger bound, boolean minInfty, boolean plusInfty) {
		this.bound = bound;
		this.isMinusInfinity = minInfty;
		this.isPlusInfinity = plusInfty;
	}
	
	public int getInt(){
		return this.bound.intValue();
	}
	
	@Override
	public String toString() {
		if(this.isPlusInfinity) {
			return "+INF";
		} else if (this.isMinusInfinity) {
			return "-INF";
		} else {
			return this.bound.toString();
		}
	}
	
	public IntervalBound add(IntervalBound b) {
		// corner cases
		if (this.isMinusInfinity && b.isPlusInfinity ||
				this.isPlusInfinity && b.isMinusInfinity)
			return null;
		
		if (this.isMinusInfinity())
			return new IntervalBound(null,true,false);
		if (this.isPlusInfinity())
			return new IntervalBound(null, false, true);
		if (b.isMinusInfinity())
			return new IntervalBound(null, true, false);
		if (b.isPlusInfinity())
			return new IntervalBound(null, false, true);
		return new IntervalBound(this.getBound().add(b.getBound()),false, false);
	}
	
	public IntervalBound sub(IntervalBound b) {
		//Corner cases
		if (this.isMinusInfinity && b.isMinusInfinity ||
				this.isPlusInfinity && b.isPlusInfinity) 
			return null;
		
		if (this.isMinusInfinity())
			return new IntervalBound(null,true,false);
		if (this.isPlusInfinity())
			return new IntervalBound(null, false, true);
		if (b.isMinusInfinity())
			return new IntervalBound(null, false, true);
		if (b.isPlusInfinity())
			return new IntervalBound(null, true, false);
		return new IntervalBound(this.getBound().subtract(b.getBound()),false, false);
	}

	public IntervalBound mul(IntervalBound b) {
		//Corner cases
		if ((this.isPlusInfinity || this.isMinusInfinity) && 
				(b.bound != null && b.bound.equals(BigInteger.ZERO))) {
			return null;
		}
		if ((b.isPlusInfinity || b.isMinusInfinity) && 
				(this.bound != null && this.bound.equals(BigInteger.ZERO))) {
			return null;
		}
		
		if (this.isMinusInfinity()) {
			if(b.isNegativ()) {
				return new IntervalBound(null,false,true);
			} else {
				return new IntervalBound(null, true, false);
			}
		}
		if (this.isPlusInfinity()) {
			if (b.isNegativ()) {
				return new IntervalBound(null, true, false);
			} else {
				return new IntervalBound(null, false, true);
			}
		}
		if (b.isMinusInfinity()) {
			if(this.isNegativ()) {
				return new IntervalBound(null,false,true);
			} else {
				return new IntervalBound(null, true, false);
			}
		}
		if (b.isPlusInfinity()) {
			if (this.isNegativ()) {
				return new IntervalBound(null, true, false);
			} else {
				return new IntervalBound(null, false, true);
			}
		}
		return new IntervalBound(this.getBound().multiply(b.getBound()),false, false);
	}
	
	private boolean isNegativ() {
		if (this.isMinusInfinity)
			return true;
		else if (this.isPlusInfinity)
			return false;
		return this.bound.signum()==-1;
	}

	public IntervalBound div(IntervalBound b) {
		// Corner cases
		if (this.isPlusInfinity && b.isPlusInfinity ||
				this.isMinusInfinity && b.isMinusInfinity ||
				this.isPlusInfinity && b.isMinusInfinity ||
				this.isMinusInfinity && b.isPlusInfinity)
			return null;
		if (b.bound != null && b.bound.equals(BigInteger.ZERO)) {
			return null;
		}
		
		
		if (this.isMinusInfinity()) {
			if(b.isNegativ()) {
				return new IntervalBound(null,false,true);
			} else {
				return new IntervalBound(null, true, false);
			}
		}
		if (this.isPlusInfinity()) {
			if (b.isNegativ()) {
				return new IntervalBound(null, true, false);
			} else {
				return new IntervalBound(null, false, true);
			}
		}
		if (b.isMinusInfinity() || b.isPlusInfinity) {
			return new IntervalBound(new BigInteger("0"), false, false);
		}
		return new IntervalBound(this.getBound().divide(b.getBound()),false, false);
	}
	
	public IntervalBound negate() {
		if (this.isMinusInfinity) {
			return new IntervalBound(null, false, true);
		} else if (this.isPlusInfinity) {
			return new IntervalBound(null, true, false);
		} else {
			return new IntervalBound(this.bound.negate(), false, false);
		}
	}
	
	public boolean eq(IntervalBound b) {
		if (this.isMinusInfinity || this.isPlusInfinity ||
				b.isMinusInfinity || b.isPlusInfinity)
			return false;
		return this.bound.equals(b.getBound());
	}
	
	public boolean lt(IntervalBound b) {
		if (this.isMinusInfinity && b.isMinusInfinity ||
				this.isPlusInfinity && b.isPlusInfinity)
			return false;
		if (this.isMinusInfinity || b.isPlusInfinity) {
			return true;
		} else if (this.isPlusInfinity || b.isMinusInfinity) {
			return false;
		} else {
			return this.bound.subtract(b.bound).signum()==-1;
		}
	}
	
	public boolean le(IntervalBound b) {
		return this.lt(b) || this.eq(b);
	}
	
	public boolean ne(IntervalBound b){
		if (b.isMinusInfinity && this.isMinusInfinity ||
				b.isPlusInfinity && this.isPlusInfinity) {
			return false;
		}
		return !this.eq(b);
	}
	
	public boolean ge(IntervalBound b){
		return this.gt(b) || this.eq(b);
	}
	
	public boolean gt(IntervalBound b){
		if (this.isMinusInfinity && b.isMinusInfinity ||
				this.isPlusInfinity && b.isPlusInfinity) {
			return false;
		}
		return !this.le(b);
	}
	
	public static IntervalBound max(IntervalBound a, IntervalBound b) {
		if (a.le(b)) {
			return b;
		} else {
			return a;
		}
	}
	
	public static IntervalBound min(IntervalBound a, IntervalBound b) {
		if (a.le(b)) {
			return a;
		} else {
			return b;
		}
	}
	
	private boolean isPlusInfinity() {
		return this.isPlusInfinity;
	}

	private boolean isMinusInfinity() {
		return this.isMinusInfinity;
	}
	
	private BigInteger getBound() {
		return this.bound;
	}
	
	@Override
	public boolean equals(Object obj) {
		if (obj instanceof IntervalBound) {
			IntervalBound b = (IntervalBound) obj;
			if (this.isMinusInfinity && b.isMinusInfinity) {
				return true;
			} else if (this.isPlusInfinity && b.isPlusInfinity) {
				return true;
			} else if (this.isMinusInfinity || this.isPlusInfinity ||
					b.isMinusInfinity || b.isPlusInfinity) {
				return false;
			} else {
				return this.getBound().equals(b.getBound());
			}
		}
		return false;
	}
	
}
